/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.spring.initializr.generator.project;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;

import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * 处理 {@link ProjectDescription}的项目生成的主入口点 通过所有可用的{@link ProjectGenerationConfiguration}类
 * 创建一个专用的{@link ProjectGenerationContext} 一旦上下文启动后 特定的
 * {@link ProjectDescription}，一个{@link ProjectAssetGenerator}可以查询它并且 生成适当的资产(例如，磁盘上的项目结构)。
 *
 * @author Andy Wilkinson
 * @author Stephane Nicoll
 */
public class ProjectGenerator {

	private final Consumer<ProjectGenerationContext> contextConsumer;

	/**
	 * 初步见解: 工程生成器自己维护一个上下文工厂, 它不与Spring的ApplicationContext主容器产生关系, 管理着项目生成需要的一些组件
	 */
	private final Supplier<? extends ProjectGenerationContext> contextFactory;

	/**
	 * 使用自定义器为项目生成器应用程序上下文创建实例 以及{@link ProjectGenerationContext}的工厂。
	 * @param contextConsumer a consumer of the project generation context after
	 * contributors and the {@link ProjectDescription} have been registered but before it
	 * is refreshed
	 * @param contextFactory the factory to use to create {@link ProjectGenerationContext}
	 * instances
	 */
	public ProjectGenerator(Consumer<ProjectGenerationContext> contextConsumer,
			Supplier<? extends ProjectGenerationContext> contextFactory) {
		this.contextConsumer = contextConsumer;
		this.contextFactory = contextFactory;
	}

	/**
	 * Create an instance with a customizer for the {@link ProjectGenerationContext} and a
	 * default factory for the {@link ProjectGenerationContext} that disables bean
	 * definition overriding.
	 * @param contextConsumer a consumer of the project generation context after
	 * contributors and the {@link ProjectDescription} have been registered but before it
	 * is refreshed
	 * @see GenericApplicationContext#setAllowBeanDefinitionOverriding(boolean)
	 */
	public ProjectGenerator(Consumer<ProjectGenerationContext> contextConsumer) {
		this(contextConsumer, defaultContextFactory());
	}

	private static Supplier<ProjectGenerationContext> defaultContextFactory() {
		return () -> {
			ProjectGenerationContext context = new ProjectGenerationContext();
			context.setAllowBeanDefinitionOverriding(false);
			return context;
		};
	}

	/**
	 * 使用特定的 {@link ProjectAssetGenerator}为特定的 {@link ProjectDescription} 生成项目资源
	 * <p>
	 * 使用提供的属性创建专用的 {@link ProjectGenerationContext}
	 * {@link #ProjectGenerator(Consumer, Supplier) contextFactory} and then apply the
	 * following:
	 * <ul>
	 * <li>根据给定的{@code description}post-processed 在给定基础上注册一个 {@link ProjectDescription}
	 * bean {@link ProjectDescriptionCustomizer} beans.</li>
	 * <li>处理所有注册的{@link ProjectGenerationConfiguration}类。</li>
	 * <li>执行 {@link #ProjectGenerator(Consumer, Supplier) contextConsumer}
	 * 在刷新上下文之前进一步定制上下文。</li>
	 * </ul>
	 * @param description the description of the project to generate
	 * @param projectAssetGenerator the {@link ProjectAssetGenerator} to invoke
	 * @param <T> the type that gathers the project assets
	 * @return the generated content
	 * @throws ProjectGenerationException if an error occurs while generating the project
	 */
	public <T> T generate(ProjectDescription description, ProjectAssetGenerator<T> projectAssetGenerator)
			throws ProjectGenerationException {

		// 通过 contextFactory 构建一个新的 ProjectGenerationContext
		try (ProjectGenerationContext context = this.contextFactory.get()) {

			// 向容器中注入ProjectDescription的Provider 由于注册的是 Provider
			// 所以这段逻辑会在 Context 执行 refresh 时运行
			context.registerBean(ProjectDescription.class, resolve(description, context));
			// 向容器中注入ProjectGenerationConfiguration代表的类
			// spring.factories 中有很多这个 SPI 的实现
			context.register(CoreConfiguration.class);

			// 调用customizeProjectGenerationContext方法
			this.contextConsumer.accept(context);
			// 刷新上下文 就是 spring 的 ApplicationContext 的刷新流程
			context.refresh();
			try {
				// 调用 projectAssetGenerator 的 generate 方法
				return projectAssetGenerator.generate(context);
			}
			catch (IOException ex) {
				throw new ProjectGenerationException("Failed to generate project", ex);
			}
		}
	}

	private Supplier<ProjectDescription> resolve(ProjectDescription description, ProjectGenerationContext context) {
		return () -> {
			if (description instanceof MutableProjectDescription) {
				MutableProjectDescription mutableDescription = (MutableProjectDescription) description;

				// context.getBeanProvider方法：返回指定bean的提供者，允许延迟按需检索实例，包括可用性和唯一性选项。
				ProjectDescriptionDiffFactory diffFactory = context.getBeanProvider(ProjectDescriptionDiffFactory.class)
						.getIfAvailable(DefaultProjectDescriptionDiffFactory::new);
				// Create the diff here so that it takes a copy of the description
				// immediately
				ProjectDescriptionDiff diff = diffFactory.create(mutableDescription);
				context.registerBean(ProjectDescriptionDiff.class, () -> diff);
				// ProjectDescriptionCustomizer 是针对 ProjectDescription 的扩展，
				// 用于对用户传入的 ProjectDescription 做调整。这里主要是一些强制依赖关系的调整，例如语言版本等
				context.getBeanProvider(ProjectDescriptionCustomizer.class).orderedStream()
						.forEach((customizer) -> customizer.customize(mutableDescription));
			}
			return description;
		};
	}

	/**
	 * {@link Configuration} class that registers all available
	 * {@link ProjectGenerationConfiguration} classes.
	 */
	@Configuration
	@Import(ProjectGenerationImportSelector.class)
	static class CoreConfiguration {

	}

	/**
	 * {@link ImportSelector} 用于加载定义在 {@code spring.factories} 清单文件中的类 using the
	 * {@code io.spring.initializr.generator.project.ProjectGenerationConfiguration} key.
	 */
	static class ProjectGenerationImportSelector implements ImportSelector {

		@Override
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
			// 通过SpringFactoriesLoader.loadFactoryNames()方法加载类的工厂实现的完全限定类名, 加载条件为：
			// 从项目路径："META-INF/spring.factories"下标注的清单中去加载且ProjectGenerationConfiguration代表的类
			List<String> factories = SpringFactoriesLoader.loadFactoryNames(ProjectGenerationConfiguration.class,
					getClass().getClassLoader());
			return factories.toArray(new String[0]);
		}

	}

}
